package net.protocol.diameter.dictionary;

import net.protocol.common.util.IoUtils;
import net.protocol.diameter.dictionary.MessageFormat.CmdF;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;

import static net.protocol.diameter.dictionary.AVPRule.DEFAULT_MAX;
import static net.protocol.diameter.dictionary.AVPRule.Level.*;

/**
 * @author jinhongw@gmail.com
 */
public class Formatter {
	public static String AvpFormat;
	public static String AvpFormat_Title;
	
	public final static String NAME = "Attribute Name";
	public final static String CODE = "Code";
	public final static String DATA_TYPE = "Data Type";

    private final static String DIR = "/"
            + Formatter.class.getPackage().getName().replaceAll("\\.", "/") + "/";
    private final static String PATH = DIR + "AVP_Format_Tile";
    
	static {
		Collection<String> lines = new ArrayList<>();
		try {
			InputStream in = Formatter.class.getResourceAsStream(PATH);
            lines.addAll(IoUtils.readAllLines(in));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		StringBuilder buf = new StringBuilder();
		for (String e : lines) {
			buf.append(e).append("\n");

			if (e.contains(Formatter.NAME) 
					&& e.contains(Formatter.CODE)
					&& e.contains(Formatter.DATA_TYPE)) {
				AvpFormat_Title = e;
			}
		}
		AvpFormat = buf.toString();
	}
	
	/**
	 * Returns a formatted string using the specified <code>MessageFormat</code> 
	 * The string returned is of the form:
	 * 
	 * <pre>
	 * Credit-Control-Answer ::= < Diameter Header: 272, PXY >
	 *                           < Session-Id >
	 *                           { Origin-Host }
	 *                        1* { Host-IP-Address AVP }
	 *                         * [ Subscription-Id ]
	 *                        *2 [ AN-GW-Address ]
	 *                         * [ Supported-Features ]
	 *                         * [ Proxy-Info ]
	 * 
	 * Credit-Control-Request ::= < Diameter Header: 272, REQ, PXY >
	 *                            < Session-Id >
	 *                            { Origin-Host }
	 *                         1* { Host-IP-Address AVP }
	 *                          * [ Subscription-Id ]
	 *                         *2 [ AN-GW-Address ]
	 *                          * [ Supported-Features ]
	 *                          * [ Proxy-Info ]
	 * </pre>
	 * 
	 * @param format Diameter Message Format
	 * @return A formatted string <code>MessageFormat</code>
	 */
    public static String format(MessageFormat format) {
		StringBuilder buf = new StringBuilder();
		// <Credit-Control-Request> ::= < Diameter Header: 272, REQ, PXY >
		buf.append(format.getFullName() == null ? format.getName() : format.getFullName() );
        buf.append(" ::");
		int length = buf.length();
		buf.append("= < Diameter Header: ").append(format.getCode());
		if (format.isRequest()) buf.append(", ").append(CmdF.REQ);
		if (format.isProxiable()) buf.append(", ").append(CmdF.PXY);
		buf.append(" >\n");
		
		for (AVPRule e : format) {
			String name = e.getAVPFormat().getName();
			for (int i = 0; i < length; i++) {
				buf.append(" ");
			}
			if (e.getLevel() == Optional) {
				switch (e.getQualQty()) {
				case Zero_One:
					buf.deleteCharAt(buf.length() - 1);
					buf.append("   ");
					break;
				case Any:
					int max = e.getMax();
					if (max != DEFAULT_MAX && max < 10) {
						buf.deleteCharAt(buf.length() - 1);
						buf.append("*").append(max).append(" ");
					} else {
						buf.deleteCharAt(buf.length() - 1);
						buf.append(" * ");
					}
					break;
				default:
				}
			}  else if (e.getLevel() == Required) {
				switch (e.getQualQty()) {
				case One_Many:
					int max = e.getMax();
					buf.deleteCharAt(buf.length() - 1);
					if (max != DEFAULT_MAX && max < 10) {
						buf.deleteCharAt(buf.length() - 1);
						buf.append("1*").append(max).append(" ");
					} else {
						buf.append("1*").append(" ");
					}
					break;
				default:
					buf.append("  ");
					break;
				}
			} else {
				buf.append("  ");
			}
			buf.append(Formatter.getL(e));
			buf.append(name);
			buf.append(Formatter.getR(e));
			buf.append("\n");
		}
        return buf.deleteCharAt(buf.length() - 1).toString();
    }
    
	/**
	 * Returns a formatted string using the specified <code>AvpFormat</code> 
	 * The string returned is of the form:
	 * 
	 * <pre>
	 *                                                                 +-----------------------+
	 *                                                                 |     AVP Flag rules    |
	 *                                                                 |-----+-----+-----+-----|----+
	 *                                     AVP                         |     |     |SHLD | MUST|    |
	 * Attribute Name                      Code      Data Type         |MUST | MAY | NOT |  NOT|Encr|
	 * ----------------------------------------------------------------|-----+-----+-----+-----|----|
	 * Proxy-State                         33        OctetString       | M   |     |     |  P,V| N  |
	 * 
	 *                                                                 +-----------------------+
	 *                                                                 |     AVP Flag rules    |
	 *                                                                 |-----+-----+-----+-----|----+
	 *                                     AVP                         |     |     |SHLD | MUST|    |
	 * Attribute Name                      Code      Data Type         |MUST | MAY | NOT |  NOT|Encr|
	 * ----------------------------------------------------------------|-----+-----+-----+-----|----|
	 * Proxy-Infot                         284       Grouped           | M   |     |     |  P,V| N  |
	 * </pre>
	 * 
	 * @param format Diameter AVP Format
	 * @return A formatted string of <code>AvpFormat</code>
	 */
    public static String format(AVPFormat format) {
		StringBuilder buf = new StringBuilder(AvpFormat);
		buf.append(format(AvpFormat_Title, format(get(format))));
		return buf.toString();
    }
    
	/**
	 * Returns a formatted string using the specified <code>AVPRule</code> 
	 * The string returned is of the form:
	 * 
	 * <pre>
	 * if (!rule.getAvpFormat().isGroup()) {
	 * 	   return rule.toString(); 
	 * } else {
	 *     Proxy-Infot ::= < AVP Header: 284 >
	 *                     { Proxy-Host }
	 *                     { Proxy-State }
	 * }
	 * </pre>
	 * 
	 * @param rule
	 *            Diameter AVP Rule
	 * @return The format string of <code>AVPRule</code>
	 */
	public static String format(AVPRule rule) {
        AVPFormat af = rule.getAVPFormat();
		if (!af.isGroup()) return rule.toString();

		StringBuilder buf = new StringBuilder();
		// Proxy-Info ::= < AVP Header: 284 >
		buf.append(af.getName()).append(" ::");
		int length = buf.length();
		buf.append("= < AVP Header: ").append(af.getCode()).append(" >\n");

		for (AVPRule e : af) {
			String name = e.getAVPFormat().getName();
			for (int i = 0; i < length; i++) {
				buf.append(" ");
			}
			buf.append("  ");
			buf.append(Formatter.getL(e));
			buf.append(name);
			buf.append(Formatter.getR(e));
			buf.append("\n");

		}
		return buf.toString();
	}
	
    /**
     * Returns a formatted string using the specified format string and arguments.
     * 
     * <pre>
     *  format -> Attribute Name                      Code      Data Type         |MUST | MAY | NOT |  NOT|Encr|
     *  return -> Vendor-Specific-Application-Id      260       DiameterIdentity  | M   |  P  |     |  V  | Y  |
     * <pre>
     *  
     * @param format format string
     * @param avpFormat Diameter AVP Format
     * @return A formatted string For example: 
     */
    public static String format(String format, AVPFormat avpFormat) {
        return format(format, format(get(avpFormat)));
    }
    
    /**
     * 
     * @param title  "Attribute Name                      Code      Data Type         |MUST | MAY | NOT |  NOT|Encr|"
     * @param args new String[]{"Product-Name", "269", "UTF8String", null, null, null, "P,M,V", "N"}
     * @return A formatted string
     */
    private static String format(String title, Object[] args) {
        return String.format(getFormat(title, args), args);
    }

	private static String getL(AVPRule e) {
		if (e.getLevel() == Fixed) return "< ";
		if (e.getLevel() == Required) return "{ ";
		return "[ ";
	}

	private static String getR(AVPRule e) {
		if (e.getLevel() == Fixed) return " >";
		if (e.getLevel() == Required) return " }";
		return " ]";
	}

    private static Object[] get(AVPFormat avpFormat) {
        Object[] a = new Object[8];
        a[0] = avpFormat.getName();
        a[1] = avpFormat.getCode();
        a[2] = avpFormat.getDataFormat().name();

        final AVPFlagRule rule = avpFormat.getFlagRule();
        a[3] = get(rule, RLevel.MUST);
        a[4] = get(rule, RLevel.MAY);
        a[5] = get(rule, RLevel.SHOULDNOT);
        a[6] = get(rule, RLevel.MUSTNOT);
        a[7] = rule.isEncrypt() ? "Y" : "N";

        return a;
    }

    private static String get(AVPFlagRule rule, RLevel level) {
        StringBuilder buf = new StringBuilder();
        if (rule.getMandatory() == level) buf.append("M");
        if (rule.getProtect() == level) {
            if (buf.length() > 0 ) buf.append(",");
            buf.append("P");
        }
        if (rule.getVendor() == level) {
            if(buf.length() > 0) buf.append(",");
            buf.append("V");
        }
        return buf.toString();
    }

    private static String getFormat(String title, Object[] args) {
        final StringBuilder buf = new StringBuilder();
        int n = title.indexOf(CODE) - title.indexOf(NAME) - 1;
        int c = title.indexOf(DATA_TYPE) - title.indexOf(CODE) - 1;
        int d = title.indexOf("|") - title.indexOf(DATA_TYPE) - 1;
        buf.append("%-").append(n).append(".").append(n).append("s ");
        buf.append("%-").append(c).append(".").append(c).append("s ");
        buf.append("%-").append(d).append(".").append(d).append("s ");

        String flag = title.substring(title.indexOf("|") + 1);
        buf.append(getFormat(flag, (String) args[3]));

        flag = flag.substring(flag.indexOf("|") + 1);
        buf.append(getFormat(flag, (String) args[4]));

        flag = flag.substring(flag.indexOf("|") + 1);
        buf.append(getFormat(flag, (String) args[5]));

        flag = flag.substring(flag.indexOf("|") + 1);
        buf.append(getFormat(flag, (String) args[6]));

        flag = flag.substring(flag.indexOf("|") + 1);
        buf.append(getFormat(flag, (String) args[7]));

        buf.append("|");

        return buf.toString();
    }

    private static Object[] format(Object[] args) {
        Object[] array = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            array[i] = (args[i] == null) ? "" : args[i];
        }
        return array;
    }

    private static String getFormat(String flag, String key) {
        final StringBuilder buf = new StringBuilder();
        int mst = flag.indexOf("|");
        if (key == null || key.trim().equals("")) {
            buf.append("|%-").append(mst).append(".").append(mst).append("s");
        } else if (key.length() == 5) {
            buf.append("|%-").append(mst).append(".").append(mst).append("s");
        } else {
            final int u = getIndex(flag);
            buf.append("|");
            for (int i = 0; i < u; i++) {
                buf.append(" ");
            }
            buf.append("%-").append(mst - u).append(".").append(mst - u).append("s");
        }
        return buf.toString();
    }

    private static int getIndex(String str) {
        char[] array = str.toCharArray();
        int index = 0;
        for (char e : array) {
            if (e == 32) {
                index++;
            } else {
                index++;
                break;
            }
        }
        return index >= 2 ? 2 : index;
    }
}
